using System;
using System.Collections.Generic;
using UnityEngine;
using static FrameWork.ObjectPoolModule;

namespace FrameWork
{
    /// <summary>
    /// 界面模块大部分功能已经完成
    /// TODO
    /// 1.自定义添加UI层级时，需要可以重新排序
    /// 2.模块内部添加界面引用计数，并且将预制体重复利用(需要先确认短时间加载key是否会返回可复用的handle)
    /// 3.代码整理
    /// </summary>
    public partial class UIModule : BaseModule
    {
        /// <summary>
        /// 界面加载模块
        /// </summary>
        private ResourceModule _resourceModule;

        /// <summary>
        /// 计时器模块
        /// </summary>
        private TimerModule _timerModule;

        /// <summary>
        /// UI辅助模块
        /// </summary>
        private IUIHelper _uiHelper;

        /// <summary>
        /// UIInstance对象池
        /// </summary>
        private ObjectPool<UIInstance> _uiInstancePool;

        /// <summary>
        /// 查找路径存储时使用，路径缓存，防止频繁反射获取
        /// </summary>
        private Dictionary<Type, ViewInfo> _viewInfoDics;

        /// <summary>
        /// 正在加载的界面
        /// </summary>
        private Dictionary<string, ViewInfo> _loadingDialogs;

        /// <summary>
        /// 正在加载的唯一key指向task加载id
        /// </summary>
        private Dictionary<string, int> _loadingUniqueKey2SerializeId;

        /// <summary>
        /// UI层级
        /// </summary>
        private Dictionary<UILayer, UIGroup> _groups;

        /// <summary>
        /// 睡眠UI计时销毁器
        /// </summary>
        private Dictionary<UIInstance, int> _sleepTimers;

        /// <summary>
        /// UI加载回调
        /// </summary>
        private LoadAssetCallBack _loadUIAssetCallBack;

        public override void Init()
        {
            _uiInstancePool = ModuleGetter.PoolModule.CreatePool<UIInstance>("UI Instance Pool");
            _viewInfoDics = new Dictionary<Type, ViewInfo>();
            _loadUIAssetCallBack = new LoadAssetCallBack(LoadDialogOnSuccess, LoadDialogOnFail);
            _loadingDialogs = new Dictionary<string, ViewInfo>();
            _loadingUniqueKey2SerializeId = new Dictionary<string, int>();
            _groups = new Dictionary<UILayer, UIGroup>();
            _sleepTimers = new Dictionary<UIInstance, int>();
            _timerModule = ModuleGetter.TimerModule;
            _resourceModule = ModuleGetter.ResourceModule;
        }

        public override void ShutDown()
        {
            _uiInstancePool.Shutdown();
            _viewInfoDics.Clear();
            _loadUIAssetCallBack = null;
            _loadingDialogs.Clear();
            _loadingUniqueKey2SerializeId.Clear();
            _sleepTimers.Clear();
        }

        public override void Update(float deltaTime, float realDeltaTime)
        {
            UpdateGroups(deltaTime);
        }

        private void UpdateGroups(float deltaTime)
        {
            foreach (var pair in _groups)
            {
                UIGroup group = pair.Value;
                group.Update(deltaTime);
            }
        }

        public int OpenView<T>() where T : BaseDialog
        {
            Type dialogType = typeof(T);
            string path = GetUIPath(dialogType);
            if (IsInLoading(path))
                return -1;

            //尝试从池子中取出对应的界面
            UIInstance uiInstance = _uiInstancePool.Spawn(path);

            int uid;

            //获取界面的info信息
            var info = GetUIInfo(dialogType);
            if (uiInstance == null)
            {
                if (_loadingUniqueKey2SerializeId.ContainsKey(info.Path))
                {
                    Debug.LogError($"无法向正在加载的界面中添加重复界面{dialogType}");
                    return 0;
                }

                uid = _resourceModule.LoadAsset<GameObject>(path, _loadUIAssetCallBack);
                _loadingUniqueKey2SerializeId.TryAdd(info.Path, uid);
                _loadingDialogs.Add(info.Path, info);
            }
            else
            {
                uid = ActiveView(info, uiInstance);
            }
            return uid;
        }

        /// <summary>
        /// 关闭界面,仅限被用于只能同时存在一个的界面
        /// </summary>
        /// <typeparam name="T">界面类型</typeparam>
        public void CloseView<T>() where T : BaseDialog
        {
            Type dialogType = typeof(T);
            ViewInfo info = GetUIInfo(dialogType);
            if (info.AllowMul)
            {
                Debug.LogError("该界面可以多次打开，无法使用该方法关闭");
                return;
            }

            UILayer uilayer = info.UILayer;
            UIGroup uIGroup = GetUIGroup(uilayer);
            UIInstance uiInstance = uIGroup.GetUIInstance(dialogType);
            uiInstance.DialogLogic.OnRelease();
            uIGroup.RemoveDialog(dialogType);
            _uiInstancePool.Unspawn(uiInstance);

            //添加ui睡眠缓存次数
            AddUIToSleep(info, uiInstance);
        }

        /// <summary>
        /// 使用id来关闭UI界面
        /// </summary>
        public void CloseView<T>(int dialogId) where T : BaseDialog
        {
            Type dialogType = typeof(T);
            ViewInfo info = GetUIInfo(dialogType);
            UILayer uilayer = info.UILayer;
            UIGroup uiGroup = GetUIGroup(uilayer);
            UIInstance uiInstance = uiGroup.GetUIInstance(dialogId);
            uiInstance.DialogLogic.OnRelease();
            uiGroup.RemoveDialog(dialogType);
            _uiInstancePool.Unspawn(uiInstance);

            AddUIToSleep(info, uiInstance);
        }

        /// <summary>
        /// 加载成功回调
        /// </summary>
        /// <param name="uniqueKey">ui路径</param>
        /// <param name="result">ui实体</param>
        /// <param name="userData">传递的UI数据</param>
        private void LoadDialogOnSuccess(string uniqueKey, object result, object userData)
        {
            GameObject prefab = result as GameObject;
            if (prefab == null)
                return;

            if (!_loadingUniqueKey2SerializeId.TryGetValue(uniqueKey, out int uiId))
            {
                Debug.LogError("获取不到正在加载的ui界面!");
                return;
            }

            if (!_loadingDialogs.TryGetValue(uniqueKey, out ViewInfo uiInfo))
            {
                Debug.LogError("找不到正在加载的界面");
                return;
            }

            //移除正在加载的ui
            _loadingDialogs.Remove(uniqueKey);
            _loadingUniqueKey2SerializeId.Remove(uniqueKey);

            GameObject dialogGo = GameObject.Instantiate(prefab);
            CreateView(uiId, dialogGo, uiInfo);
        }

        /// <summary>
        /// 加载ui失败
        /// </summary>
        /// <param name="uniqueKey">UI路径</param>
        /// <param name="errorMessage">错误信息</param>
        private void LoadDialogOnFail(string uniqueKey, string errorMessage,object userData)
        {
            Debug.LogError($"资源{uniqueKey}加载出错{errorMessage}");

            //移除正在加载的ui
            _loadingDialogs.Remove(uniqueKey);
        }

        /// <summary>
        /// 获取对应UI界面的路径
        /// </summary>
        /// <param name="dialogType"></param>
        /// <returns></returns>
        private string GetUIPath(Type dialogType)
        {
            if (_viewInfoDics.TryGetValue(dialogType, out ViewInfo info))
            {
                return info.Path;
            }

            string infoClass = dialogType.ToString() + "UIInfo";
            Type infoType = Type.GetType(infoClass);
            var viewInfo = Activator.CreateInstance(infoType) as ViewInfo;

            if (viewInfo != null)
            {
                _viewInfoDics[dialogType] = viewInfo;
            }
            else
            {
                throw new NullReferenceException("转化ViewInfo为空!");
            }

            return viewInfo.Path;
        }

        /// <summary>
        /// 获取界面信息
        /// </summary>
        /// <param name="dialogType"></param>
        /// <returns></returns>
        private ViewInfo GetUIInfo(Type dialogType)
        {
            return _viewInfoDics[dialogType];
        }

        /// <summary>
        /// 创建一个新界面
        /// </summary>
        /// <param name="uniqueKey">界面路径key</param>
        /// <param name="serializeId">唯一id</param>
        /// <param name="uiLogic">界面管理器</param>
        /// <param name="viewInfo">界面信息</param>
        private void CreateView(int serializeId, GameObject dialogGo, ViewInfo viewInfo)
        {
            var uiLogic = _uiHelper.ExecuteUIDialog(dialogGo, viewInfo);
            var uiInstance = UIInstance.CreateInstance(serializeId, uiLogic, viewInfo.Path);
            _uiInstancePool.Register(uiInstance, true);
            var group = GetUIGroup(viewInfo.UILayer);
            group.AddUIDialog(uiInstance);
            uiLogic.OnShow();
        }

        /// <summary>
        /// 激活UI View
        /// </summary>
        /// <param name="serializeId">UI新的Id</param>
        /// <param name="viewInfo">UI信息</param>
        /// <param name="uiInstance">UI实例</param>
        private int ActiveView(ViewInfo viewInfo, UIInstance uiInstance)
        {
            //停止界面睡眠计时
            int timerId = _sleepTimers[uiInstance];
            _timerModule.StopTimer(timerId);

            //Mono层
            _uiHelper.ExecuteUIDialog(uiInstance.GameObject, viewInfo);

            var group = GetUIGroup(viewInfo.UILayer);
            uiInstance.Active();
            group.AddUIDialog(uiInstance);
            uiInstance.DialogLogic.OnShow();

            _sleepTimers.Remove(uiInstance);
            return uiInstance.DialogId;
        }

        /// <summary>
        /// 获取某个界面
        /// </summary>
        /// <typeparam name="T">UI泛型</typeparam>
        /// <returns>UI界面</returns>
        public T GetDialog<T>() where T : BaseDialog
        {
            Type t = typeof(T);
            ViewInfo uiInfo = GetUIInfo(t);
            if (uiInfo.AllowMul) Debug.LogWarning("警告,该界面会重复出现,如出现错误请用id查询");
            UIGroup uiGroup = GetUIGroup(uiInfo.UILayer);
            UIInstance uiInstance = uiGroup.GetUIInstance(t);
            return uiInstance.GetDialog<T>();
        }

        /// <summary>
        /// 通过界面id来查询界面，适用于多个界面可以共存的情况
        /// </summary>
        /// <param name="serializeId">唯一id</param>
        /// <returns>界面管理器</returns>
        public T GetDialog<T>(int serializeId) where T : BaseDialog
        {
            Type t = typeof(T);
            ViewInfo uiInfo = GetUIInfo(t);
            UIGroup uiGroup = GetUIGroup(uiInfo.UILayer);
            UIInstance uiInstance = uiGroup.GetUIInstance(serializeId);
            return uiInstance.GetDialog<T>();
        }

        /// <summary>
        /// 获取对应层级的UI组
        /// </summary>
        /// <param name="uiLayer">UIlayer</param>
        /// <returns>UI组</returns>
        private UIGroup GetUIGroup(UILayer uiLayer)
        {
            if (!_groups.ContainsKey(uiLayer))
            {
                UIGroup group = new UIGroup();
                _groups.Add(uiLayer, group);
            }

            return _groups[uiLayer];
        }

        /// <summary>
        /// 检查某个界面是否正在加载
        /// </summary>
        private bool IsInLoading(string uniqueKey)
        {
            return _loadingDialogs.ContainsKey(uniqueKey);
        }

        /// <summary>
        /// UI添加至睡眠状态
        /// </summary>
        private void AddUIToSleep(ViewInfo viewInfo, UIInstance uIInstance)
        {
            string uniqueKey = viewInfo.Path;
            float sleepTime = GetSleepTime(viewInfo.SleepTime);

            //处理Mono层
            GameObject go = uIInstance.GameObject;
            _uiHelper.AddUIToSleep(go.transform);

            //延时内的时间内，如果界面再次被调用，需要将该id从计时器中移除
            int timerId = _timerModule.DelayTime(sleepTime, () =>
            {
                ReleaseUI(uIInstance);
            });

            if (!_sleepTimers.ContainsKey(uIInstance))
            {
                _sleepTimers.Add(uIInstance, timerId);
            }
            else
            {
                Debug.LogError("睡眠UI中已经包含该UIInstnace了");
            }
        }

        /// <summary>
        /// 返回界面的回收间隔时间
        /// </summary>
        private float GetSleepTime(float sleepTime)
        {
            //先用默认的界面回收时间
            float time = _uiHelper.RecycleInterval;

            if (sleepTime != 0)
            {
                time = Math.Max(sleepTime, 0);
            }

            return time;
        }

        private void ReleaseUI(UIInstance uiInstance)
        {
            _sleepTimers.Remove(uiInstance);
            int dialogId = uiInstance.DialogId;
            _uiInstancePool.ReleaseObject(uiInstance);
            _resourceModule.ReleaseAsset(dialogId);
        }

        public void RegisterHelper(IUIHelper uiHelper)
        {
            _uiHelper = uiHelper;
        }
    }
}

